﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace RaptorEngine.Components.CollidableComponent
{
	/// <summary>
	/// Handles and holds all information about boundingVolumes (Sphere or box), 
	/// the collision check and the action to take on a collision.
	/// </summary>
	public class Collidable : Component
	{
		public Dictionary<string, BoundingVolume> BoundingVolumes { get; private set; }

	    /// <summary>
	    /// Creates a collidable component to be added to a entity.
	    /// Handles the collision check between collidable entities and holds the action to be perform
	    /// when a collision is detected. 
	    /// Creates a boundingVolume to hold the model.
	    /// </summary>
	    /// <param name="e">The mediator entity</param>
	    /// <param name="action">The action to be perform when a collision is detected</param>
	    /// <param name="model">The model to create a first boundingVolume around</param>
	    /// <param name="useSphere">If true, a boundingSphere will hold the model. If false, a boundingBox will be used.</param>
	    /// <param name="collisionType"></param>
	    public Collidable(Entity e, CollisionAction action, Model model, bool useSphere, int collisionType)
			: base(e, ComponentType.Collidable)
		{
            BoundingVolumes = new Dictionary<string, BoundingVolume>();
			
			// Create the boundingSphere from the model 
			BoundingSphere sphere = new BoundingSphere();
			foreach (ModelMesh mesh in model.Meshes)
			{
				if (sphere.Radius == 0)
				{
					sphere = mesh.BoundingSphere;
				}
				else
				{
					sphere = BoundingSphere.CreateMerged(sphere, mesh.BoundingSphere);
						
				}
			}
			sphere.Center = Entity.Position;
			sphere.Radius *= Entity.Scale;

			// Add the boundingSphere or use it to make and add a boundingBox.
			if (useSphere)
			{
                BoundingVolumes.Add("default", new BoundingVolume(sphere, Vector3.Zero, collisionType) { Action = action } );
			}
			else
			{
				// float cubeRadius = sphere.Radius/2;
				// float cubeRadius = (float)(Math.Sqrt(Math.Pow(sphere.Radius, 2)) / Math.Sqrt(2));
				// MagicNumber to get inner cube radius instead of outer cube radius.
				float cubeRadius = sphere.Radius / 2.15f; 
				
				BoundingBox box = new BoundingBox(Entity.Position + new Vector3(-cubeRadius), Entity.Position + new Vector3(cubeRadius));
                BoundingVolumes.Add("default", new BoundingVolume(box, Vector3.Zero, cubeRadius, sphere.Center, collisionType) { Action = action } );
			}
		}

		/// <summary>
		/// Creates a collidable component to be added to a entity.
		/// Handles the collision check between collidable entities and holds the action to be perform
		/// when a collision is detected. 
		/// </summary>
		/// <param name="e">The mediator entity</param>
		/// <param name="action">The action to be perform when a collision is detected</param>
		public Collidable(Entity e)
			: base(e, ComponentType.Collidable)
		{
			BoundingVolumes = new Dictionary<string, BoundingVolume>();
		}

		/// <summary>
		/// Add an additional boundingSphere
		/// </summary>
		/// <param name="center">The position of the entity</param>
		/// <param name="offset">The offset from the center</param>
		/// <param name="radius">The radius of the boundingSphere</param>
		public void AddBoundingSphere(string name, Vector3 center, Vector3 offset, float radius, int collisionType, CollisionAction action)
		{
			var sphere = new BoundingSphere(center, radius);
            BoundingVolumes.Add(name, new BoundingVolume(sphere, offset, collisionType) { Action = action } );
		}

		public void AddBoundingBox(string name, Vector3 center, Vector3 offset, float radius, int collisionType)
		{
			Vector3 min = center - new Vector3(radius);
			Vector3 max = center + new Vector3(radius);
			BoundingVolumes.Add(name, new BoundingVolume(new BoundingBox(min, max), offset, radius, center, collisionType));
		}

		/// <summary>
		/// Update the entity's boundingVolumes based on the entity's new position.
		/// </summary>
		public void UpdateBoundingVolumes()
		{
			foreach (KeyValuePair<string, BoundingVolume> boundingVolume in BoundingVolumes)
			{
				boundingVolume.Value.UpdateBoundingVolume(Entity.Position, Entity.Rotation);
			}
		}

		/// <summary>
		/// Check if there is a collision between the current component and another entity's collidable component. 
		/// </summary>
		/// <param name="checkAgainst">The component to check collision against</param>
		public void CheckCollision(Collidable checkAgainst, GameTime gameTime)
		{
			// Loop through all the boundingVolumes for both components
			foreach (var ownVolume in BoundingVolumes)
			{
                if(ownVolume.Value.IsActive){
                    foreach (var othersVolume in checkAgainst.BoundingVolumes)
                    {
                        if (othersVolume.Value.IsActive)
                        {
                            // Check what type of collision to test
                            if (ownVolume.Value.VolumeType == VolumeType.Sphere)
                            {
                                // Sphere vs Sphere
                                if (othersVolume.Value.VolumeType == VolumeType.Sphere)
                                {
                                    // Check if there is a collision
                                    if (ownVolume.Value.Sphere.Value.Intersects(othersVolume.Value.Sphere.Value))
                                    {
                                        // Handle the collision for both components
                                        HandleCollision(ownVolume.Value, othersVolume.Value, checkAgainst.Entity, gameTime);
                                    }
                                }
                                // Sphere vs Box
                                else
                                {
                                    // Check if there is a collision
                                    if (ownVolume.Value.Sphere.Value.Intersects(othersVolume.Value.Box.Value))
                                    {
                                        // Handle the collision for both components
                                        HandleCollision(ownVolume.Value, othersVolume.Value, checkAgainst.Entity, gameTime);
                                    }
                                }
                            }
                            else
                            {
                                // Box vs Box
                                if (othersVolume.Value.VolumeType == VolumeType.Box)
                                {
                                    // Check if there is a collision
                                    if (ownVolume.Value.Box.Value.Intersects(othersVolume.Value.Box.Value))
                                    {
                                        // Handle the collision for both components
                                        HandleCollision(ownVolume.Value, othersVolume.Value, checkAgainst.Entity, gameTime);
                                    }
                                }
                                // Box vs Sphere
                                else
                                {
                                    // Check if there is a collision
                                    if (ownVolume.Value.Box.Value.Intersects(othersVolume.Value.Sphere.Value))
                                    {
                                        // Handle the collision for both components
                                        HandleCollision(ownVolume.Value, othersVolume.Value, checkAgainst.Entity, gameTime);
                                    }
                                }
                            }
                        }
                    }
				}
			}
		}

        private void HandleCollision(BoundingVolume ownVolume, BoundingVolume othersVolume, Entity otherEntity, GameTime gameTime)
        {
            if(ownVolume.Action != null)
                ownVolume.Action.HandleCollision(Entity, ownVolume, otherEntity, othersVolume);
            if (othersVolume.Action != null)
                othersVolume.Action.HandleCollision(otherEntity, othersVolume, Entity, ownVolume);                          
        }
	}
}
